package asteroids.model.programs;

import java.util.*;

import exceptions.IllegalOperandException;

import asteroids.model.expressions.*;
import asteroids.model.statements.*;
import asteroids.model.types.*;
import asteroids.model.*;

import be.kuleuven.cs.som.annotate.*;


/**
 * A class that represents a program
 * 
 * In this class all statements en expression are put together. The class than runs all statements in the right order.
 * 
 * @version 1.0
 * 
 * @author	Thijs Peirelinck & Wouter Parys
 *
 */
public class Program {
	
	private S statement;
	private Map<String, T> globals;
	private boolean isTerminated = false;
	private HashMap<String, E> variables;
	private Iterator<S> it;

	/**
	 * 
	 * @param globals
	 * 			a map of global variables and their type
	 * @param statement
	 * 			the sequence of statements that represents this program
	 * @post	the statement of this program is the given statement
	 * 			| (new this).getStatement() == statement
	 * @post	the globals of this program are the given globals
	 * 			| (new this).getGlobals() == globals
	 * @effect	all the global variables are initialized
	 * 			| initializeVariables()
	 */
	public Program(Map<String, T> globals, S statement) {
		this.statement = statement;
		this.globals = globals;		
		variables = new HashMap<String, E>();
		initializeVariables();
	}
	
	/**
	 * Initilialize all the global variables
	 */
	@Model
	private void initializeVariables() {
		String[] globalArray = globals.keySet().toArray(new String[0]);
		E[] defaultValues = new E[globals.size()];
		for(int i = 0; i < globals.size(); i++) {
			if(globals.get(globalArray[i]).getReturnType().equals("double"))
				defaultValues[i] = new DoubleLiteral(0);
			if(globals.get(globalArray[i]).getReturnType().equals("entity"))
				defaultValues[i] = new EntityLiteral(null);
			if(globals.get(globalArray[i]).getReturnType().equals("boolean"))
				defaultValues[i] = new BooleanLiteral(false);
		}
		for(int i = 0; i < globals.size(); i++)
			variables.put(globalArray[i], defaultValues[i]);
	}
	
	/**
	 * TypeCheck this program
	 * 
	 * @param globals
	 * @return	result == this.getStatement().typeCheck(globals)
	 */
	public boolean typeCheck(Map<String, T> globals) {
		return this.getStatement().typeCheck(globals);
	}
	
	/**
	 * Set the executer of this program in all the expressions and statements that belong to this program
	 * @param ship
	 * @effect	the executer of the statement is set
	 * 			| this.getStatement().setExecuter(ship)
	 */
	public void setShip(Ship ship) {
		getStatement().setExecuter(ship);
	}
	
	/**
	 * terminates this program
	 * 
	 * @post	this program is terminated
	 * 			| this.isTerminated()
	 */
	public void terminate() {
		isTerminated = true;
	}
	
	/**
	 * Returns if this program is terminated or not
	 * @return	result == isTerminated
	 */
	public boolean isTerminated() {
		return isTerminated;
	}
	
	@Basic @Raw
	public Map<String, T> getGlobals(){
		return globals;
	}
	
	@Basic
	public S getStatement() {
		return statement;
	}
	
	/**
	 * Execute this program until the next action
	 * 
	 * @param ship
	 */
	public void doNext(Ship ship) {
		if(!this.isTerminated()) {
			Action action = getNextAction();
			if(action != null) {
				if(action instanceof Turn) {
					((Turn)action).setValidatedAngle(((Turn)action).getValidatedExpressionCopy(((Turn)action).getExpression(), variables));
				}
				try {
					action.action();
				} catch (IllegalOperandException exc) {
					this.terminate();
				}
			}
		}
	}
	
	/**
	 * returns the next action of this program
	 *
	 * @return	the next action
	 */
	public Action getNextAction() {
		if (!this.isTerminated()) {
			if (it == null) {
				this.it = ((Sequence)statement).iterator(variables);
			}
			try {
				while (it.hasNext()) {
					S s = it.next();
					if(s instanceof BasicStatement) {
						((BasicStatement)s).doStatement(variables);
					}
					if(s instanceof Action) {
						return (Action)s;				
					}
				}
			} catch (IllegalOperandException exc) {
				this.terminate();
				return null;
			}
			
		}
		this.terminate();
		return null;
	}
	
}
